{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using dill to pickle anything"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ipyparallel doesn't do much in the way of serialization.\n",
    "It has custom zero-copy handling of numpy arrays,\n",
    "but other than that, it doesn't do anything other than the bare minimum to make basic interactively defined functions and classes sendable.\n",
    "\n",
    "There are a few projects that extend pickle to make just about anything sendable, and one of these is [dill](http://www.cacr.caltech.edu/~mmckerns/dill).\n",
    "\n",
    "To install dill:\n",
    "        \n",
    "    pip install dill"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, as always, we create a task function, this time with a closure"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def make_closure(a):\n",
    "    \"\"\"make a weird function with a closure on an open file, and return it\"\"\"\n",
    "    import os\n",
    "    f = open('/tmp/dilltest', 'a')\n",
    "    def has_closure(b):\n",
    "        product = a * b\n",
    "        f.write(\"%i: %g\\n\" % (os.getpid(), product))\n",
    "        f.flush()\n",
    "        return product\n",
    "    return has_closure"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "!rm -f /tmp/dilltest\n",
    "closed = make_closure(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "closed(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "74025: 10\r\n"
     ]
    }
   ],
   "source": [
    "cat /tmp/dilltest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import pickle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Without help, pickle can't deal with closures"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "Can't pickle local object 'make_closure.<locals>.has_closure'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-6-0f1f376cfea0>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclosed\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m: Can't pickle local object 'make_closure.<locals>.has_closure'"
     ]
    }
   ],
   "source": [
    "pickle.dumps(closed)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But after we import dill, magic happens"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import dill"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x80\\x03cdill.dill\\n_create_function\\nq\\x00(cdill.dill\\n_load_type\\nq\\x01X\\x08\\x00\\x00\\x00Co...'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dill.dumps(closed)[:64] + b'...'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So from now on, pretty much everything is pickleable."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Now use this in ipyparallel"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As usual, we start by creating our Client and View"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import ipyparallel as parallel\n",
    "rc = parallel.Client()\n",
    "view = rc.load_balanced_view()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's try sending our function with a closure:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "cannot serialize '_io.TextIOWrapper' object",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-10-23a646829fdc>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mview\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapply_sync\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclosed\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m/Users/benjaminrk/dev/ip/parallel/ipyparallel/client/view.py\u001b[0m in \u001b[0;36mapply_sync\u001b[1;34m(self, f, *args, **kwargs)\u001b[0m\n\u001b[0;32m    224\u001b[0m          \u001b[0mreturning\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m.\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    225\u001b[0m         \"\"\"\n\u001b[1;32m--> 226\u001b[1;33m         \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_really_apply\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mblock\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    227\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    228\u001b[0m     \u001b[1;31m#----------------------------------------------------------------\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<decorator-gen-148>\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n",
      "\u001b[1;32m/Users/benjaminrk/dev/ip/parallel/ipyparallel/client/view.py\u001b[0m in \u001b[0;36msync_results\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m     50\u001b[0m     \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_in_sync_results\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     51\u001b[0m     \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 52\u001b[1;33m         \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     53\u001b[0m     \u001b[1;32mfinally\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     54\u001b[0m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_in_sync_results\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mFalse\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<decorator-gen-147>\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n",
      "\u001b[1;32m/Users/benjaminrk/dev/ip/parallel/ipyparallel/client/view.py\u001b[0m in \u001b[0;36msave_ids\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m     35\u001b[0m     \u001b[0mn_previous\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclient\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhistory\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     36\u001b[0m     \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 37\u001b[1;33m         \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     38\u001b[0m     \u001b[1;32mfinally\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     39\u001b[0m         \u001b[0mnmsgs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclient\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhistory\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mn_previous\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/Users/benjaminrk/dev/ip/parallel/ipyparallel/client/view.py\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n\u001b[0;32m   1030\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1031\u001b[0m         future = self.client.send_apply_request(self._socket, f, args, kwargs, track=track,\n\u001b[1;32m-> 1032\u001b[1;33m                                 metadata=metadata)\n\u001b[0m\u001b[0;32m   1033\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1034\u001b[0m         ar = AsyncResult(self.client, future, fname=getname(f),\n",
      "\u001b[1;32m/Users/benjaminrk/dev/ip/parallel/ipyparallel/client/client.py\u001b[0m in \u001b[0;36msend_apply_request\u001b[1;34m(self, socket, f, args, kwargs, metadata, track, ident)\u001b[0m\n\u001b[0;32m   1300\u001b[0m         bufs = serialize.pack_apply_message(f, args, kwargs,\n\u001b[0;32m   1301\u001b[0m             \u001b[0mbuffer_threshold\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbuffer_threshold\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1302\u001b[1;33m             \u001b[0mitem_threshold\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mitem_threshold\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1303\u001b[0m         )\n\u001b[0;32m   1304\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/Users/benjaminrk/dev/ip/parallel/ipyparallel/serialize/serialize.py\u001b[0m in \u001b[0;36mpack_apply_message\u001b[1;34m(f, args, kwargs, buffer_threshold, item_threshold)\u001b[0m\n\u001b[0;32m    150\u001b[0m     \u001b[0minfo\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnargs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnarg_bufs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0marg_bufs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkw_keys\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mkw_keys\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    151\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 152\u001b[1;33m     \u001b[0mmsg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcan\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mPICKLE_PROTOCOL\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    153\u001b[0m     \u001b[0mmsg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minfo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mPICKLE_PROTOCOL\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    154\u001b[0m     \u001b[0mmsg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0marg_bufs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mTypeError\u001b[0m: cannot serialize '_io.TextIOWrapper' object"
     ]
    }
   ],
   "source": [
    "view.apply_sync(closed, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Oops, no dice. For IPython to work with dill,\n",
    "there are one or two more steps. IPython will do these for you if you call `DirectView.use_dill`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<module 'ipyparallel.serialize' from '/Users/benjaminrk/dev/ip/parallel/ipyparallel/serialize/__init__.py'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<AsyncResult: use_dill>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rc[:].use_dill()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is equivalent to\n",
    "\n",
    "```python\n",
    "from ipyparallel.canning import use_dill\n",
    "use_dill()\n",
    "rc[:].apply(use_dill)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's try again"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "view.apply_sync(closed, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "74025: 10\r\n",
      "74113: 15\r\n"
     ]
    }
   ],
   "source": [
    "cat /tmp/dilltest"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Yay! Now we can use dill to allow ipyparallel to send anything.\n",
    "\n",
    "And that's it! We can send closures, open file handles, and other previously non-pickleables to our engines.\n",
    "\n",
    "Let's give it a try now:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "20"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "remote_closure = view.apply_sync(make_closure, 4)\n",
    "remote_closure(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "74025: 10\r\n",
      "74113: 15\r\n",
      "74025: 20\r\n"
     ]
    }
   ],
   "source": [
    "cat /tmp/dilltest"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But wait, there's more!\n",
    "\n",
    "At this point, we can send/recv all kinds of stuff"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def outer(a):\n",
    "    def inner(b):\n",
    "        def inner_again(c):\n",
    "            return c * b * a\n",
    "        return inner_again\n",
    "    return inner"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So outer returns a function with a closure, which returns a function with a closure.\n",
    "\n",
    "Now, we can resolve the first closure on the engine, the second here, and the third on a different engine,\n",
    "after passing through a lambda we define here and call there, just for good measure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "view.apply_sync(lambda f: f(3),view.apply_sync(outer, 1)(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And for good measure, let's test that normal execution still works:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[5, 5, 5, 5, 5, 5, 5, 5]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[10, 10, 10, 10, 10, 10, 10, 10]"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%px foo = 5\n",
    "\n",
    "print(rc[:]['foo'])\n",
    "rc[:]['bar'] = lambda : 2 * foo\n",
    "rc[:].apply_sync(parallel.Reference('bar'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And test that the `@interactive` decorator works"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting testdill.py\n"
     ]
    }
   ],
   "source": [
    "%%file testdill.py\n",
    "import ipyparallel as ipp\n",
    "\n",
    "@interactive\n",
    "class C(object):\n",
    "    a = 5\n",
    "\n",
    "@ipp.interactive\n",
    "class D(C):\n",
    "    b = 10\n",
    "\n",
    "@interactive\n",
    "def foo(a):\n",
    "    return a * b\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import testdill"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5 10\n"
     ]
    }
   ],
   "source": [
    "v = rc[-1]\n",
    "v['D'] = testdill.D\n",
    "d = v.apply_sync(lambda : D())\n",
    "print(d.a, d.b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v['b'] = 10\n",
    "v.apply_sync(testdill.foo, 5)"
   ]
  }
 ],
 "metadata": {
  "gist_id": "5241793",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
